Sejauh ini struktur halaman selalu hidup di template JSON (23 Templates) yang ngerakit section dan block. Bab ini soal arah baru yang lagi diuji Shopify: nyusun halaman langsung di Liquid pakai {% block %}, dan nyegarin sebagian halaman tanpa reload pakai {% partial %}.

Ini developer preview, artinya perilakunya masih bisa berubah sebelum rilis umum. Buat nyoba, aktifin Liquid July ‘26 changes di feature previews dev store kamu, terus mulai dari skeleton theme release candidate atau tambahin tag-nya ke theme sendiri. Masukan ditampung di Shopify developer community.

Theme yang udah ada tetap jalan. Section, theme setting, dan template JSON semuanya masih didukung. Preview ini nambahin cara Liquid-first, bukan ngeganti.

Apa yang Berubah

Sekarang, struktur halaman ada di template JSON, terpisah dari Liquid yang ngerendernya. Buat ngerti satu halaman, kamu harus buka JSON-nya, terus buka tiap section yang dirujuk, terus tiap block di dalamnya. Dengan {% block %}, struktur itu pindah ke template, jadi kamu buka satu file dan baca dari atas ke bawah. Shopify juga nyebut alasan yang relevan buat zaman sekarang: coding agent bisa baca seluruh template, bukan ngerekonstruksi halaman dari JSON dan pohon section.

Halaman di model ini disusun dari potongan yang udah familiar:

  1. Liquid template, nentuin struktur halaman dan ngerender HTML di server.
  2. Block, markup yang bisa dipakai ulang dengan setting theme editor opsional, dirender pakai {% block %}.
  3. Snippet, utilitas internal dan Liquid yang berulang.
  4. HTML, konten spesifik halaman yang ditulis langsung di template.
  5. Web component, perilaku browser yang ditambahin ke HTML hasil render.
  6. Partial, region hasil render server yang bisa di-refresh JavaScript, ditandai {% partial %}.

Gambaran Utuh

Template koleksi yang ngerender heading dan grid produk, dan nandain cuma grid-nya sebagai region yang bisa diganti:

{% block 'container', class: 'collection' %}
  <h1>{{ collection.title }}</h1>
  {% partial 'product-grid' %}
    {% for product in collection.products %}
      {% block 'product-card', block.settings.product: product %}{% endblock %}
    {% endfor %}
  {% endpartial %}
{% endblock %}

Tiap {% block %} ngerender file dengan nama yang sama dari blocks/ dan ngirim data ke situ. {% partial %} ngasih nama ke grid produk, jadi JavaScript bisa ngambil HTML baru dan ngeganti cuma region itu waktu pembeli sort atau filter.

Bandingin sama cara sekarang di 29 Theme Blocks Mendalam > 3. Ngirim Sumber Daya Lewat Liquid: {% content_for "block", type: "product-card", id: "card", closest.product: product %} di dalam section, yang dirakit template JSON. Di sini gak ada JSON dan gak ada section, langsung template → block.

Tag block

Block di sini itu theme block yang sama dari 28 Blocks dan 29 Theme Blocks Mendalam: file Liquid di blocks/ dengan markup, perilaku, atribut aksesibilitas, dan setting theme editor dalam satu file. Yang baru cara naruhnya di halaman: bukan cuma lewat theme editor, tapi template Liquid bisa ngerendernya langsung.

Cara pakainya kaya {% render %} ngerender snippet: sebut nama file block, kirim parameter bernama yang dia terima, dan opsional kasih body content.

1. Sintaks Dasar

Nama block dipetakan ke file di blocks/. {% block 'container' %} ngerender blocks/container.liquid:

{% block 'container' %}
  <h1>Welcome</h1>
{% endblock %}

Semua yang ada di antara tag buka dan tutup itu body content block. File block-nya nyetak body content itu pakai {{ block.content }}:

{% assign tag = tag | default: 'div' %}
<{{ tag }} class="{{ class }}">
  {{ block.content }}
</{{ tag }}>
{% schema %}
{
  "name": "t:blocks.container",
  "settings": []
}
{% endschema %}

{% schema %} tetap tempat block nyatain setting theme editor-nya, di contoh ini belum ada. Ini yang bikin {% block %} beda dari {% render %}: snippet gak bisa punya body content, dan snippet gak punya schema.

2. Parameter Bernama

Sama kaya {% render %}, kirim parameter setelah nama block. Kode Liquid block yang nentuin tiap parameter dipakai buat apa:

{% block 'container', tag: 'header', class: 'site-header' %}
  <h1>Page title</h1>
{% endblock %}

Di sini tag dan class dipakai block buat milih elemen HTML dan nambahin class CSS. Perhatiin: class gak punya perilaku platform khusus di sini (beda sama atribut class di schema dari 29 Theme Blocks Mendalam > 7. class), dia cuma variabel biasa yang block-nya cetak sendiri, jadi styling kamu tetap kelihatan di Liquid yang ngerender HTML-nya.

3. Ngeset Setting Langsung: block.settings.*

Schema block nentuin setting yang dibaca sebagai block.settings.<id>. Biasanya merchant yang ngisi lewat theme editor. Dari pemanggilan {% block %}, kamu bisa ngeset langsung pakai parameter block.settings.*, berguna waktu template udah tau nilainya:

{% block 'button', block.settings.variant: 'button-primary' %}
  Add to cart
{% endblock %}

Ini ngeset setting variant ke button-primary, nilai yang sama yang biasanya block baca dari theme editor. Parameter biasa dan setting bisa dikirim bareng:

{% block 'product-card',
  class: 'featured-product',
  block.settings.product: product
%}
{% endblock %}

4. Array Literal

Parameter block bisa nerima array literal buat daftar pendek yang hidup di template:

{% block 'badge-list',
  badges: ['New arrival', 'Low stock', 'Online only']
%}
{% endblock %}

Berlaku juga buat block.settings.* yang nunjuk ke setting schema (misalnya collection_list):

{% block 'collection-list',
  block.settings.collections: [collections['summer'], collections['sale']]
%}
{% endblock %}

Catatan: array literal inline cuma ada di {% block %}. {% render %} dan {% partial %} gak nerima array yang ditulis langsung di tag. Ini hal baru di Liquid, karena selama ini bikin array cuma bisa lewat split dari 18 Filters String, Math, dan Array.

5. Body Content

Body content bisa markup biasa, block lain, atau campuran. Template produk yang ngirim heading dan block button bersarang ke dalam container:

{% block 'container' %}
  <h1>{{ product.title }}</h1>
  {% block 'button', type: 'submit', class: 'button--full-width' %}
    Add to cart
  {% endblock %}
{% endblock %}

Aturan desainnya: kalau block cuma nampilin teks atau markup, kirim sebagai body content, bukan parameter kaya title, body, atau heading. Tambahin parameter cuma kalau block perlu ngelakuin sesuatu sama nilainya, misalnya ngubah cara render atau baca data yang dikirim.

❌ parameter buat teks yang cuma dicetak

{% block 'button', label: 'Add to cart' %}{% endblock %}

✅ body content

{% block 'button' %}Add to cart{% endblock %}

6. Nulis File Block

File block berisi markup Liquid plus dua tag yang udah kamu kenal:

  1. {% doc %} dari 07 Tools Lokal VS Code Extension, Prettier, LiquidDoc buat ndokumentasiin parameter bernama yang diterima dan contoh pemanggilannya.
  2. {% schema %} buat setting di theme editor: pilihan tampilan, resource picker, setting komponen kontekstual.

Prinsip pembagiannya: parameter buat nilai yang dikontrol template, setting buat nilai yang dikontrol merchant. Template ngeset type tombol, merchant milih variant-nya:

{% doc %}
  @param {string} [type] - HTML button type.
{% enddoc %}
<button type="{{ type }}" class="{{ block.settings.variant }}">
  {{ block.content }}
</button>
{% schema %}
{
  "name": "t:blocks.button",
  "settings": [
    {
      "type": "select",
      "id": "variant",
      "label": "t:settings.variant",
      "options": [
        {"value": "button-primary", "label": "t:options.primary"}
      ]
    }
  ]
}
{% endschema %}

Jaga block tetap fokus: usahakan enam setting atau kurang, kalau butuh jauh lebih banyak, pecah jadi block lebih kecil. Apapun yang dikontrol template harus jadi parameter, bukan setting.

Kalau nilai bisa datang dari parameter atau setting, kamu yang nentuin urutannya di Liquid block. Tombol ini pakai parameter variant kalau template ngirim, fallback ke setting, terakhir ke default:

{% assign variant = variant | default: block.settings.variant | default: 'button-primary' %}
<button class="{{ variant }}">
  {{ block.content }}
</button>

7. block vs content_for vs render

Tiga tag ini sama-sama ngerender kode yang bisa dipakai ulang. Pilihannya tergantung siapa yang ngontrol halaman dan apa yang dirender:

  1. {% block %}: waktu template ngontrol halaman. Kamu nyebut block-nya, ngirim parameter dan body content, dan seluruh komposisinya tetap di Liquid yang bisa dibaca dari atas ke bawah.
  2. {% content_for %}: waktu merchant ngontrol halaman dengan nambah dan nyusun block di theme editor, yang Shopify simpan di template JSON. Ini cara dari 28 Blocks.
  3. {% render %}: buat snippet di snippets/, Liquid internal yang berulang dan gak punya setting theme editor.

Tag partial

Partial itu region bernama dari HTML hasil render server yang bisa di-refresh JavaScript sendiri, tanpa reload halaman penuh. Kamu nandainnya inline di template atau layout pakai {% partial %}, jadi gak ada endpoint terpisah atau kode render sisi klien yang harus dirawat.

Sampai sekarang, ngupdate sebagian halaman tanpa reload berarti pakai Section Rendering API dari 51 Ajax API dan Section Rendering API plus framework sisi klien buat nyambungin responsnya balik ke halaman. Partial ngasih perilaku itu dari web platform, dengan tiga bagian:

  1. Liquid ngerender HTML region-nya di server.
  2. JavaScript ngambil HTML baru buat region itu dan nerapinnya ke halaman.
  3. Nama partial nyambungin respons ke region yang diganti, jadi update-nya mendarat di tempat yang benar.

Ini dibangun di atas proposal web platform Declarative Partial Updates, bukan sesuatu yang cuma ada di Shopify.

1. Sintaks Dasar

Bungkus region dengan {% partial %} dan {% endpartial %}, kasih nama. Isinya Liquid biasa yang dirender bareng sisa halaman waktu load pertama:

{% partial 'product-grid' %}
  {% for product in collection.products %}
    {% render 'product-card', product: product %}
  {% endfor %}
{% endpartial %}

Bungkus cuma bagian yang berubah, bukan seluruh halaman. Sort atau filter koleksi cuma perlu nyegarin grid produk, jadi grid itu yang layak dikasih nama. Nama ini yang jadi target JavaScript nanti: waktu JavaScript minta product-grid, template harus punya {% partial 'product-grid' %} yang cocok.

2. fetch() dan apply()

JavaScript ngupdate partial dalam dua langkah: minta HTML baru buat region dari server, terus nerapin HTML itu ke halaman. Package @shopify/partial-rendering nyediain helper buat dua-duanya.

fetch() minta HTML baru buat satu atau lebih partial bernama dan ngembaliin hasilnya. apply() ngambil hasil itu dan ngeganti region yang cocok. Manggil dua-duanya sendiri ngasih kamu kontrol atas request (URL, method, body) dan waktu update-nya.

Contoh ngupdate product-grid dengan urutan sort baru, URL-nya dibangun dari URL halaman sekarang biar locale dan query parameter lain tetap:

import {partials} from '@shopify/partial-rendering';
const url = new URL(window.location.href);
url.searchParams.set('sort_by', 'price-ascending');
const update = await partials.fetch('product-grid', {
  url: url.toString(),
});
partials.apply(update);

Bangun URL dari halaman sekarang atau object routes dari 13 Objects Global > routes, jangan hardcode path storefront, biar request tetap jalan lintas locale dan market.

Satu interaksi sering ngubah lebih dari satu region. Filter koleksi ngupdate grid produk, jumlah hasil, dan filter aktif. Ambil bareng biar satu request nyinkronin semua:

const update = await partials.fetch(
  'product-grid',
  'product-count',
  'active-filters',
  {url: url.toString()},
);
partials.apply(update);

Bawaannya apply() nuker konten langsung. Buat animasi transisi, panggil apply() di dalam View Transition, dengan fallback buat browser yang gak dukung:

if (document.startViewTransition) {
  document.startViewTransition(() => partials.apply(update));
} else {
  partials.apply(update);
}

3. refresh()

Pakai refresh() kalau request-nya ke URL halaman sekarang, jadi gak ada URL yang perlu dibangun. Contoh: waktu tombol back/forward browser ngubah URL, panggil refresh() buat nyinkronin region yang terpengaruh. Dia fetch dan apply dalam satu panggilan:

await partials.refresh('product-grid', 'product-count');

Buat ngupdate semuanya setelah perubahan yang ngaruh ke banyak region (ganti mata uang atau bahasa), panggil tanpa argumen. Atau kirim elemen buat refresh cuma partial di dalamnya:

await partials.refresh();
await partials.refresh(document.querySelector('[data-product-list]'));

4. get() dan getAll()

Kadang kamu cuma perlu ngerujuk region yang udah ada tanpa fetch atau ganti, misalnya buat ngukur. get() ngembaliin partial pertama dengan nama tertentu, getAll() ngembaliin semua partial dalam satu scope. Tiap hasil ngepasangin nama partial dengan Range yang mencakup isinya:

const grid = partials.get('product-grid');
const bounds = grid?.range.getBoundingClientRect();
const regions = partials.getAll(document.querySelector('main'));

5. Aksesibilitas dan State

Update partial ngeganti konten di tempat, jadi browser ngelewatin urusan rumah tangga yang biasanya dia lakuin waktu load halaman penuh. apply() ngurus sebagian: fokus, seleksi teks, nilai form, dan posisi scroll dibawa melewati pertukaran. Sisanya tanggung jawab kode kamu:

  1. Respons basi. Request lama yang lambat bisa selesai setelah yang baru dan nimpanya. Kirim AbortSignal ke fetch() buat ngebatalin request yang lagi jalan waktu yang baru mulai, biar interaksi terakhir yang menang.
  2. Umpan balik loading. Gak ada yang nandain request lagi jalan. Set aria-busy di region selama update.
  3. Pengumuman. Pertukaran DOM yang diam gak diumumin ke screen reader. Pakai live region buat pesan sukses dan error yang berarti.
  4. UI sementara. State yang cuma hidup di DOM (disclosure yang lagi terbuka) reset waktu region diganti, jadi harus dipulihin setelah update.
  5. Nilai milik server. Biarin markup yang dikembaliin jadi sumber kebenaran buat nilai yang dimiliki server. Setelah update cart, jumlah item datang dari respons, bukan dari DOM sebelum update. Buat state yang hidup di URL, baca dari window.location.search biar link yang dibagiin dan navigasi back/forward mendarat di hasil yang sama.

Bahasan aksesibilitas secara umum ada di 55 Accessibility.

Kapan Ini Relevan Buat Kamu

Tiga hal yang perlu dipegang:

  1. Ini preview. Jangan dipakai di theme produksi atau theme yang mau disubmit ke Theme Store sampai rilis umum. Requirements Theme Store di 70 Theme Store Gambaran dan Requirements belum ngenal tag ini.
  2. Konsepnya gak asing. {% block %} itu {% render %} yang punya body content dan schema. {% partial %} itu Section Rendering API tanpa section. Kalau kamu paham 12 Tags Khusus Theme > render, 29 Theme Blocks Mendalam, dan 51 Ajax API dan Section Rendering API, kamu udah paham 80%-nya.
  3. Arahnya jelas. Shopify lagi ngedorong struktur halaman balik ke kode yang bisa dibaca (dan dibaca AI). Theme yang kamu bangun sekarang dengan theme block yang rapi dan fokus bakal gampang diadaptasi ke model ini.

Ringkasan

  1. Developer preview Liquid July ‘26 nambahin dua tag: {% block %} buat nyusun halaman dari theme block langsung di template Liquid, dan {% partial %} buat nyegarin sebagian halaman tanpa reload. Theme lama tetap jalan.
  2. {% block 'nama' %}...{% endblock %} ngerender blocks/nama.liquid, kaya render tapi dengan body content ({{ block.content }}) dan schema.
  3. Parameter bernama buat nilai yang dikontrol template, block.settings.* buat ngeset setting schema langsung, dua-duanya bisa bareng, dan array literal inline cuma ada di tag ini.
  4. Teks yang cuma dicetak dikirim sebagai body content, bukan parameter.
  5. File block: {% doc %} buat parameter, {% schema %} buat setting merchant, maksimal sekitar enam setting, dan urutan fallback parameter → setting → default ditentuin di Liquid block.
  6. Pilih tag: block kalau template ngontrol, content_for kalau merchant ngontrol lewat editor dan JSON, render buat snippet tanpa setting.
  7. {% partial 'nama' %} nandain region yang bisa diganti, bungkus cuma bagian yang berubah.
  8. @shopify/partial-rendering: fetch() + apply() buat kontrol penuh (URL dari halaman sekarang atau routes, bisa banyak region sekaligus, bisa View Transition), refresh() buat URL sekarang (tanpa argumen = semua), get()/getAll() buat ngerujuk region.
  9. apply() ngejaga fokus, seleksi, nilai form, scroll, sisanya tugas kamu: AbortSignal buat respons basi, aria-busy, live region, pulihin UI sementara, dan percaya nilai dari server.

Bab selanjutnya kita mulai bagian baru: fitur storefront. Pertama navigasi dan menu, dari object linklists dan link, menu bersarang, sampai cara nampilin state aktif dan bikin dropdown yang aksesibel.